/*
    serial driver
    Copyright (C) 2011  Jiabo <jiabo2011@gmail.com>

    All functions copied from qemu 0.5.1

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

#include "../ioport.h"

#define DEBUG_SERIAL
#ifdef DEBUG_SERIAL
#define DPRINTF(fmt, ...)                                       \
    do { printf("SERIAL: " fmt , ## __VA_ARGS__); } while (0)
#else
#define DPRINTF(fmt, ...)
#endif

/*
 * These are the definitions for the Modem Control Register
 */
#define UART_MCR_LOOP	0x10	/* Enable loopback test mode */
#define UART_MCR_OUT2	0x08	/* Out2 complement */
#define UART_MCR_OUT1	0x04	/* Out1 complement */
#define UART_MCR_RTS	0x02	/* RTS complement */
#define UART_MCR_DTR	0x01	/* DTR complement */

typedef struct SerialState {
	uint8_t divider;
	uint8_t rbr; /* receive register */
	uint8_t ier;
	uint8_t iir; /* read only */
	uint8_t lcr;
	uint8_t mcr;
	uint8_t lsr; /* read only */
	uint8_t msr;
	uint8_t scr;
	/* NOTE: this hidden state is necessary for tx irq generation as
	it can be reset while reading iir */
	int thr_ipending;
} SerialState;

static void serial_ioport_write(void *opaque, uint32_t addr, uint32_t val)
{
	SerialState *s = opaque;

	addr &= 7;
	DPRINTF("write addr=0x%02x val=0x%02x\n", addr, val);

	switch(addr) {
	default:
	case 0:
		break;
	case 4:
		s->mcr = val;
		break;

	}
}

static uint32_t serial_ioport_read(void *opaque, uint32_t addr)
{
	SerialState *s = opaque;
	uint32_t ret = 0;

	addr &= 7;
	switch(addr) {
	default:
	case 0:
		break;
	case 4:
		ret = s->mcr;
		break;
	case 6:
		if (s->mcr & UART_MCR_LOOP) {
			/* in loopback, the modem output pins are connected to the
			inputs */
			ret = (s->mcr & 0x0c) << 4;
			ret |= (s->mcr & 0x02) << 3;
			ret |= (s->mcr & 0x01) << 5;
		} else {
			ret = s->msr;
		}
		break;
	}
	DPRINTF("read addr=0x%02x val=0x%02x\n", addr, ret);
	return ret;
}

void serial_init(void)
{
	SerialState *s;
	s = calloc(1, sizeof(SerialState));

	register_ioport_write(0x3f8, 8, 1, serial_ioport_write, s);
	register_ioport_read(0x3f8, 8, 1, serial_ioport_read, s);
}